//============================================================================
// Name        : BPlusTree.cpp
// Author      : dhonegger
// Version     :
// Copyright   : cmpe226
// Description : Hello World in C++, Ansi-style
//============================================================================

#include <iostream>
#include <stdlib.h>
#include <time.h>
#include <limits.h>
#include <boost/thread.hpp>
#include <boost/bind.hpp>
#include <fstream>

#include "../StorageManager/StorageManager.h"
#include "../tree_structures.h"
#include "../tree_functions.h"

using namespace std;

#define MAX_RECORDS 2000000

#define PART1 0
#define PART2 0
#define PART4 1

//Global declarations of storage manager and indexes for console ouput
StorageManager *sm = new StorageManager();
Index *milIdx = new Index(64);
Index *twoMilIdx = new Index(64);
Record *recPtr;

//Used to measure performance by calculating run time in mS
double clockElapsedToMs(clock_t start, clock_t end)
{
	double diffticks=end-start;
	double diffms=(diffticks*1000)/CLOCKS_PER_SEC;
	return diffms;
}

/********************************************************************************
 * The following classes are used for invoking threads with various
 * search/insert ratios
 *******************************************************************************/

class search100 {
public:
	search100(int reps): reps(reps) {};
	void search100_f()
	{
		clock_t start = clock();
		for(int idx = 0; idx < reps; idx++) {
			recPtr = milIdx->searchRecord(rand() % INT_MAX);
		}
		elapsedTime = clockElapsedToMs(start, clock());
	}
	double elapsedTime;
	int reps;
};

class search90_insert10 {
public:
	search90_insert10(int reps): reps(reps) {};
	void search90_insert10_f2() {
		numInsert = 0;
		numSearch = 0;
		clock_t start = clock();
		for(int idx = 0; idx < reps; idx++) {
				if((rand() % 10) == 1) {
						recPtr = sm->FetchRecord(1000000 + idx);
						if(!(twoMilIdx->insertRecord(recPtr->GetStudent()->GetSsn(), recPtr)))
								cout << "There is already a record with the same key!!!!!!!!!!!!!!!!!!" << endl;
						numInsert++;
				}
				else {
						recPtr = milIdx->searchRecord(rand() % INT_MAX);
						numSearch++;
				}
		}
		randomOpMs = clockElapsedToMs(start, clock());
	}
	double randomOpMs;
	int numInsert, numSearch, reps;
};

class search70_insert30 {
public:
	search70_insert30(int reps): reps(reps) {};
	void search70_insert30_f() {
		numInsert = 0;
		numSearch = 0;
		clock_t start = clock();
		for(int idx = 0; idx < reps; idx++) {
				if((rand() % 10) < 3) {
						recPtr = sm->FetchRecord(1000000 + idx);
						if(!(twoMilIdx->insertRecord(recPtr->GetStudent()->GetSsn(), recPtr)))
								cout << "There is already a record with the same key!!!!!!!!!!!!!!!!!!" << endl;
						numInsert++;
				}
				else {
						recPtr = milIdx->searchRecord(rand() % INT_MAX);
						numSearch++;
				}
		}
		randomOpMs = clockElapsedToMs(start, clock());
	}
	double randomOpMs;
	int numInsert, numSearch, reps;
};

class search50_insert50 {
public:
	search50_insert50(int reps): reps(reps) {};
	void search50_insert50_f() {
		numInsert = 0;
		numSearch = 0;
		clock_t start = clock();
		for(int idx = 0; idx < reps; idx++) {
				if(rand() % 2) {
						recPtr = sm->FetchRecord(1000000 + idx);
						if(!(twoMilIdx->insertRecord(recPtr->GetStudent()->GetSsn(), recPtr)))
								cout << "There is already a record with the same key!!!!!!!!!!!!!!!!!!" << endl;
						numInsert++;
				}
				else {
						recPtr = milIdx->searchRecord(rand() % INT_MAX);
						numSearch++;
				}
		}
		randomOpMs = clockElapsedToMs(start, clock());
	}
	double randomOpMs;
	int numInsert, numSearch, reps;
};




int main() {
	cout << "BPlusTree" << endl << "RAND_MAX: " << RAND_MAX << endl << endl; //prints Test Storage Manager
	ofstream myfile;
	myfile.open("output.txt");
	//preallocate 2 million records


	Page *pg;
	srand(clock());
	unsigned int randKey;

	int idx;

	for(idx = 0; idx < MAX_RECORDS; idx++) {
		pg = sm->AllocatePage();
		randKey = rand() % INT_MAX;
		pg->GetRecordLocation()->SetStudent(randKey, "last", "first", "addy", rand() % 100);
	}

	if(PART1) {
		cout << "PART 1" << endl;
		Record *recPtr;
		Index *demoIdx = new Index(20);
		for(idx = 0; idx < 100; idx++)
		{
			recPtr = sm->FetchRecord(idx);
			if(demoIdx->searchRecord(recPtr->GetStudent()->GetSsn()) == NULL)
				demoIdx->insertRecord(recPtr->GetStudent()->GetSsn(), recPtr);
			else
				cout << "index collision on: " << recPtr->GetStudent()->GetSsn() << endl;
		}

		demoIdx->printIndex();
		//demoIdx->printKeys();
		cout << endl << endl;

		unsigned int searchKey;
		bool searching = true;
		unsigned int prevKey;

		while(searching) {
			cout << "Input key to search for (enter 0 twice to exit)-->" << endl;
			cin >> searchKey;
			if(prevKey == searchKey && searchKey == 0)
				break;
			prevKey = searchKey;

			recPtr = demoIdx->searchRecord(searchKey);
			if(recPtr == NULL)
				cout << "record not found" << endl;
			else
				cout << "found, info: " << recPtr->GetStudent()->GetStudentString() << endl;
		}

		delete demoIdx;
	}

    if(PART2) {
            cout << "PART 2" << endl;
            Record *recPtr;
            double milMs, twoMilMs, searchMs, randomOpMs;
            int numSearch = 0, numInsert = 0;

            Index *milIdx = new Index(64);
            Index *twoMilIdx = new Index(64);

            //Part A
            clock_t start = clock();
            for(idx = 0; idx < 1000000; idx++)
            {
                    recPtr = sm->FetchRecord(idx);
                    if(!(milIdx->insertRecord(recPtr->GetStudent()->GetSsn(), recPtr)))
                            cout << "There is already a record with the same key!!!!!!!!!!!!!!!!!!" << endl;
            }
            milMs = clockElapsedToMs(start, clock());

            start = clock();
            for(idx = 0; idx < 2000000; idx++)
            {
                    recPtr = sm->FetchRecord(idx);
                    if(!(twoMilIdx->insertRecord(recPtr->GetStudent()->GetSsn(), recPtr)))
                            cout << "There is already a record with the same key!!!!!!!!!!!!!!!!!!" << endl;
            }
            twoMilMs = clockElapsedToMs(start, clock());

            //Part B
            start = clock();
            for(idx = 0; idx < 100000; idx++) {
                    recPtr = twoMilIdx->searchRecord(rand() % INT_MAX);
            }
            searchMs = clockElapsedToMs(start, clock());

            //Part C
            start = clock();
            for(idx = 0; idx < 100000; idx++) {
                    if(rand() % 2) {
                            recPtr = sm->FetchRecord(1000000 + idx);
                            if(!(milIdx->insertRecord(recPtr->GetStudent()->GetSsn(), recPtr)))
                                    cout << "There is already a record with the same key!!!!!!!!!!!!!!!!!!" << endl;
                            numInsert++;
                    }
                    else {
                            recPtr = milIdx->searchRecord(rand() % INT_MAX);
                            numSearch++;
                    }
            }
            randomOpMs = clockElapsedToMs(start, clock());

            cout << "Time for 1,000,000 inserts: " << milMs << " ms" << endl
                            << "Time for 2,000,000 inserts: " << twoMilMs << " ms" << endl
                            << "Time for 100,000 searches: " << searchMs << " ms" << endl
                            << "Time for " << numInsert << " inserts, " << numSearch << " searches: " << randomOpMs << " ms" << endl;

            delete milIdx;
            delete twoMilIdx;
    }

    //Multi-Threading Performance Cases
	if(PART4) {
		cout << "PART 4" << endl;
		double milMs;

		//Loads the index with 2,000,000 records from the storage manager
		clock_t start = clock();
		for(idx = 0; idx < 2000000; idx++)
		{
			recPtr = sm->FetchRecord(idx);
			if(!(twoMilIdx->insertRecord(recPtr->GetStudent()->GetSsn(), recPtr)))
				cout << "There is already a record with the same key!!!!!!!!!!!!!!!!!!" << endl;
		}
		milMs = clockElapsedToMs(start, clock());

		myfile << "Time for 2,000,000 inserts: " << milMs << " ms" << endl << endl;

		/***********************************************************************************
		 * The below test cases measure performance of 100% search, 90%/10% search/insert,
		 * 70%/30% search/insert and 50%/50% search insert.  In each case a baseline single
		 * thread case is used to gauge multi-threaded performance by.
		 **********************************************************************************/


		search100 s100_0(100000);
		boost::function<void()> s100_thread0 = boost::bind(&search100::search100_f, &s100_0);
		boost::thread t0(s100_thread0);
		t0.join();


		myfile << "100% Searches:" << endl << endl;
		myfile << "Single Thread Base Case:" << endl;
		myfile << "Searches: 100,000\tTime: " << s100_0.elapsedTime << " ms" << endl;

		search100 s100_1(50000);
		search100 s100_2(50000);
		boost::function<void()> s100_thread1 = boost::bind(&search100::search100_f, &s100_1);
		boost::function<void()> s100_thread2 = boost::bind(&search100::search100_f, &s100_2);
		boost::thread t1(s100_thread1);
		boost::thread t2(s100_thread2);
		t1.join();
		t2.join();

		myfile << "Multi-Threaded Case:" << endl;
		myfile << "Thread 1\t Searches: 50,000\tTime: " << s100_1.elapsedTime << " ms" << endl;
		myfile << "Thread 2\t Searches: 50,000\tTime: " << s100_2.elapsedTime << " ms" << endl << endl;



		search90_insert10 s90i10_0(100000);
		boost::function<void()> s90i10_thread0 = boost::bind(&search90_insert10::search90_insert10_f2, &s90i10_0);
		boost::thread t3(s90i10_thread0);
		t3.join();

		myfile << "90% Searches 10% Inserts:" << endl << endl;
		myfile << "Single Thread Base Case:" << endl;
		myfile << "Searches: " << s90i10_0.numSearch << " Inserts: " << s90i10_0.numInsert << "\tTime:" << s90i10_0.randomOpMs << " ms" << endl;

		search90_insert10 s90i10_1(50000);
		search90_insert10 s90i10_2(50000);
		boost::function<void()> s90i10_thread1 = boost::bind(&search90_insert10::search90_insert10_f2, &s90i10_1);
		boost::function<void()> s90i10_thread2 = boost::bind(&search90_insert10::search90_insert10_f2, &s90i10_2);
		boost::thread t4(s90i10_thread1);
		boost::thread t5(s90i10_thread2);
		t4.join();
		t5.join();

		myfile << "Multi-Threaded Case:" << endl;
		myfile << "Thread 1\t" << "Searches: " << s90i10_1.numSearch << " Inserts: " << s90i10_1.numInsert << "\tTime:" << s90i10_1.randomOpMs << " ms" << endl;
		myfile << "Thread 2\t" << "Searches: " << s90i10_2.numSearch << " Inserts: " << s90i10_2.numInsert << "\tTime:" << s90i10_2.randomOpMs << " ms" << endl << endl;

		search70_insert30 s70i30_0(100000);
		boost::function<void()> s70i30_single = boost::bind(&search70_insert30::search70_insert30_f, &s70i30_0);
		boost::thread t10(s70i30_single);
		t10.join();

		myfile << "70% Searches 30% Inserts:" << endl << endl;
		myfile << "Single Thread Base Case:" << endl;
		myfile << "Searches: " << s70i30_0.numSearch << " Inserts: " << s70i30_0.numInsert << "\tTime:" << s70i30_0.randomOpMs << " ms" << endl;

		search70_insert30 s70i30_1(50000);
		search70_insert30 s70i30_2(50000);
		boost::function<void()> s70i30_thread1 = boost::bind(&search70_insert30::search70_insert30_f, &s70i30_1);
		boost::function<void()> s70i30_thread2 = boost::bind(&search70_insert30::search70_insert30_f, &s70i30_2);
		boost::thread t6(s70i30_thread1);
		boost::thread t7(s70i30_thread2);
		t6.join();
		t7.join();

		myfile << "Multi-Threaded Case:" << endl;
		myfile << "Thread 1\t" << "Searches: " << s70i30_1.numSearch << " Inserts: " << s70i30_1.numInsert << "\tTime:" << s70i30_1.randomOpMs << " ms" << endl;
		myfile << "Thread 2\t" << "Searches: " << s70i30_2.numSearch << " Inserts: " << s70i30_2.numInsert << "\tTime:" << s70i30_2.randomOpMs << " ms" << endl << endl;

		search50_insert50 s50i50_0(100000);
		boost::function<void()> s50i50_single = boost::bind(&search50_insert50::search50_insert50_f, &s50i50_0);
		boost::thread t11(s50i50_single);
		t11.join();

		myfile << "50% Searches 50% Inserts:" << endl << endl;
		myfile << "Single Thread Base Case:" << endl;
		myfile << "Searches: " << s50i50_0.numSearch << " Inserts: " << s50i50_0.numInsert << "\tTime:" << s50i50_0.randomOpMs << " ms" << endl;

		search50_insert50 s50i50_1(50000);
		search50_insert50 s50i50_2(50000);
		boost::function<void()> s50i50_thread1 = boost::bind(&search50_insert50::search50_insert50_f, &s50i50_1);
		boost::function<void()> s50i50_thread2 = boost::bind(&search50_insert50::search50_insert50_f, &s50i50_2);
		boost::thread t8(s50i50_thread1);
		boost::thread t9(s50i50_thread2);
		t8.join();
		t9.join();

		myfile << "Thread 1\t" << "Searches: " << s50i50_1.numSearch << " Inserts: " << s50i50_1.numInsert << "\tTime:" << s50i50_1.randomOpMs << " ms" << endl;
		myfile << "Thread 2\t" << "Searches: " << s50i50_2.numSearch << " Inserts: " << s50i50_2.numInsert << "\tTime:" << s50i50_2.randomOpMs << " ms" << endl << endl;

		myfile.close();
		delete milIdx;
		delete twoMilIdx;
	}


	delete sm;

	cout << endl << "Performance logged.  Press enter to exit..." << endl;

	string input;
	cin >> input;

	return 0;
}



